查看原文
其他

SpringBoot 注解原理,自动装配原理,图文并茂,万字长文!

首先,先看SpringBoot的主配置类:

  1. @SpringBootApplication

  2. public class StartEurekaApplication

  3. {

  4.    public static void main(String[] args)

  5.    {

  6.        SpringApplication.run(StartEurekaApplication.class, args);

  7.    }

  8. }

点进@SpringBootApplication来看,发现@SpringBootApplication是一个组合注解。

  1. @Target(ElementType.TYPE)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @SpringBootConfiguration

  6. @EnableAutoConfiguration

  7. @ComponentScan(excludeFilters = {

  8.      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

  9.      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

  10. public @interface SpringBootApplication {

  11. }

我们先来看 

十大城市,程序员工作内推群

十大城市,程序员工作内推群

@SpringBootConfiguration:

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Configuration

  5. public @interface SpringBootConfiguration {

  6. }

可以看到这个注解除了元注解以外,就只有一个@Configuration,那也就是说这个注解相当于@Configuration,所以这两个注解作用是一样的,它让我们能够去注册一些额外的Bean,并且导入一些额外的配置。那@Configuration还有一个作用就是把该类变成一个配置类,不需要额外的XML进行配置。所以@SpringBootConfiguration就相当于@Configuration。进入@Configuration,发现@Configuration核心是@Component,说明Spring的配置类也是Spring的一个组件。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Component

  5. public @interface Configuration {

  6.    @AliasFor(

  7.        annotation = Component.class

  8.    )

  9.    String value() default "";

  10. }

继续来看下一个

@EnableAutoConfiguration

这个注解是开启自动配置的功能。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @AutoConfigurationPackage

  6. @Import({AutoConfigurationImportSelector.class})

  7. public @interface EnableAutoConfiguration {

  8.    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

  9.    Class<?>[] exclude() default {};

  10.    String[] excludeName() default {};

  11. }

可以看到它是由 

@AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)

这两个而组成的,我们先说

@AutoConfigurationPackage

他是说:让包中的类以及子包中的类能够被自动扫描到spring容器中。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @Import({Registrar.class})

  6. public @interface AutoConfigurationPackage {

  7. }

使用@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class。来看下这个Registrar:

  1.    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

  2.        Registrar() {

  3.        }

  4.        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

  5.            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());

  6.        }

  7.        public Set<Object> determineImports(AnnotationMetadata metadata) {

  8.            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));

  9.        }

  10.    }

就是通过以上这个方法获取扫描的包路径,可以debug查看具体的值:

那metadata是什么呢,可以看到是标注在@SpringBootApplication注解上的DemosbApplication,也就是我们的主配置类Application:


其实就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。因此我们要把DemoApplication放在项目的最高级中(最外层目录)。

看看注解@Import(AutoConfigurationImportSelector.class),@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector。

可以从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector,ImportSelector有一个方法为:selectImports。将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {

  2.    if (!this.isEnabled(annotationMetadata)) {

  3.        return NO_IMPORTS;

  4.    } else {

  5.        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

  6.        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =

  7. this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);

  8.        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

  9.    }

  10. }

会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件。

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。那是如何获取到这些配置类的呢,看看下面这个方法:

  1. protected AutoConfigurationImportSelector.AutoConfigurationEntry

  2. getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {

  3.    if (!this.isEnabled(annotationMetadata)) {

  4.        return EMPTY_ENTRY;

  5.    } else {

  6.        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

  7.        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

  8.        configurations = this.removeDuplicates(configurations);

  9.        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

  10.        this.checkExcludedClasses(configurations, exclusions);

  11.        configurations.removeAll(exclusions);

  12.        configurations = this.filter(configurations, autoConfigurationMetadata);

  13.        this.fireAutoConfigurationImportEvents(configurations, exclusions);

  14.        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

  15.    }

  16. }

我们可以看到getCandidateConfigurations()这个方法,他的作用就是引入系统已经加载好的一些类,到底是那些类呢:

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

  2.    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

  3.    Assert.notEmpty(configurations,

  4. "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");

  5.    return configurations;

  6. }

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {

  2.    String factoryClassName = factoryClass.getName();

  3.    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

  4. }

会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

  2.    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);

  3.    if (result != null) {

  4.        return result;

  5.    } else {

  6.        try {

  7.            Enumeration<URL> urls = classLoader !=

  8. null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

  9.            LinkedMultiValueMap result = new LinkedMultiValueMap();

  10.            while(urls.hasMoreElements()) {

  11.                URL url = (URL)urls.nextElement();

  12.                UrlResource resource = new UrlResource(url);

  13.                Properties properties = PropertiesLoaderUtils.loadProperties(resource);

  14.                Iterator var6 = properties.entrySet().iterator();

  15.                while(var6.hasNext()) {

  16.                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();

  17.                    String factoryClassName = ((String)entry.getKey()).trim();

  18.                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());

  19.                    int var10 = var9.length;

  20.                    for(int var11 = 0; var11 < var10; ++var11) {

  21.                        String factoryName = var9[var11];

  22.                        result.add(factoryClassName, factoryName.trim());

  23.                    }

  24.                }

  25.            }

  26.            cache.put(classLoader, result);

  27.            return result;

  28.        } catch (IOException var13) {

  29.            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);

  30.        }

  31.    }

  32. }

可以知道SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。如下图可以发现Spring常见的一些类已经自动导入。


接下来看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),这个注解就是扫描包,然后放入spring容器。


  1. @ComponentScan(excludeFilters = {

  2. @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),

  3. @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})

  4. public@interfaceSpringBootApplication{}

总结下@SpringbootApplication:就是说,他已经把很多东西准备好,具体是否使用取决于我们的程序或者说配置。

接下来继续看run方法:

  1. publicstaticvoid main(String[] args) {

  2.        SpringApplication.run(Application.class, args);

  3.    }

来看下在执行run方法到底有没有用到哪些自动配置的东西,我们点进run:

  1. publicConfigurableApplicationContext run(String... args) {

  2.    //计时器

  3.    StopWatch stopWatch = newStopWatch();

  4.    stopWatch.start();

  5.    ConfigurableApplicationContext context = null;

  6.    Collection<SpringBootExceptionReporter> exceptionReporters = newArrayList();

  7.    this.configureHeadlessProperty();

  8.    //监听器

  9.    SpringApplicationRunListeners listeners = this.getRunListeners(args);

  10.    listeners.starting();

  11.    Collection exceptionReporters;

  12.    try{

  13.        ApplicationArguments applicationArguments = newDefaultApplicationArguments(args);

  14.        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

  15.        this.configureIgnoreBeanInfo(environment);

  16.        Banner printedBanner = this.printBanner(environment);

  17.        //准备上下文

  18.        context = this.createApplicationContext();

  19.        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,

  20. newClass[]{ConfigurableApplicationContext.class}, context);

  21.        //预刷新context

  22.        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

  23.        //刷新context

  24.        this.refreshContext(context);

  25.        //刷新之后的context

  26.        this.afterRefresh(context, applicationArguments);

  27.        stopWatch.stop();

  28.        if(this.logStartupInfo) {

  29.            (newStartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);

  30.        }

  31.        listeners.started(context);

  32.        this.callRunners(context, applicationArguments);

  33.    } catch(Throwable var10) {

  34.        this.handleRunFailure(context, var10, exceptionReporters, listeners);

  35.        thrownewIllegalStateException(var10);

  36.    }

  37.    try{

  38.        listeners.running(context);

  39.        return context;

  40.    } catch(Throwable var9) {

  41.        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);

  42.        thrownewIllegalStateException(var9);

  43.    }

  44. }

那我们关注的就是 refreshContext(context); 刷新context,我们点进来看。

  1. privatevoid refreshContext(ConfigurableApplicationContext context) {

  2.   refresh(context);

  3.   if(this.registerShutdownHook) {

  4.      try{

  5.         context.registerShutdownHook();

  6.      }

  7.      catch(AccessControlException ex) {

  8.         // Not allowed in some environments.

  9.      }

  10.   }

  11. }

我们继续点进refresh(context);

  1. protectedvoid refresh(ApplicationContext applicationContext) {

  2.   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);

  3.   ((AbstractApplicationContext) applicationContext).refresh();

  4. }

他会调用 ((AbstractApplicationContext) applicationContext).refresh();方法,我们点进来看:

  1. publicvoid refresh() throwsBeansException, IllegalStateException{

  2.   synchronized(this.startupShutdownMonitor) {

  3.      // Prepare this context for refreshing.

  4.      prepareRefresh();

  5.      // Tell the subclass to refresh the internal bean factory.

  6.      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

  7.      // Prepare the bean factory for use in this context.

  8.      prepareBeanFactory(beanFactory);

  9.      try{

  10.         // Allows post-processing of the bean factory in context subclasses.

  11.         postProcessBeanFactory(beanFactory);

  12.         // Invoke factory processors registered as beans in the context.

  13.         invokeBeanFactoryPostProcessors(beanFactory);

  14.         // Register bean processors that intercept bean creation.

  15.         registerBeanPostProcessors(beanFactory);

  16.         // Initialize message source for this context.

  17.         initMessageSource();

  18.         // Initialize event multicaster for this context.

  19.         initApplicationEventMulticaster();

  20.         // Initialize other special beans in specific context subclasses.

  21.         onRefresh();

  22.         // Check for listener beans and register them.

  23.         registerListeners();

  24.         // Instantiate all remaining (non-lazy-init) singletons.

  25.         finishBeanFactoryInitialization(beanFactory);

  26.         // Last step: publish corresponding event.

  27.         finishRefresh();

  28.      }catch(BeansException ex) {

  29.         if(logger.isWarnEnabled()) {

  30.            logger.warn("Exception encountered during context initialization - "+

  31.                  "cancelling refresh attempt: "+ ex);

  32.         }

  33.         // Destroy already created singletons to avoid dangling resources.

  34.         destroyBeans();

  35.         // Reset 'active' flag.

  36.         cancelRefresh(ex);

  37.         // Propagate exception to caller.

  38.         throw ex;

  39.      }finally{

  40.         // Reset common introspection caches in Spring's core, since we

  41.         // might not ever need metadata for singleton beans anymore...

  42.         resetCommonCaches();

  43.      }

  44.   }

  45. }

由此可知,就是一个spring的bean的加载过程。继续来看一个方法叫做 onRefresh():

  1. protectedvoid onRefresh() throwsBeansException{

  2.   // For subclasses: do nothing by default.

  3. }

他在这里并没有直接实现,但是我们找他的具体实现:

 

比如Tomcat跟web有关,我们可以看到有个ServletWebServerApplicationContext:

  1. @Override

  2. protectedvoid onRefresh() {

  3.   super.onRefresh();

  4.   try{

  5.      createWebServer();

  6.   }

  7.   catch(Throwable ex) {

  8.      thrownewApplicationContextException("Unable to start web server", ex);

  9.   }

  10. }

可以看到有一个createWebServer();方法他是创建web容器的,而Tomcat不就是web容器,那是如何创建的呢,我们继续看:

  1. privatevoid createWebServer() {

  2.   WebServer webServer = this.webServer;

  3.   ServletContext servletContext = getServletContext();

  4.   if(webServer == null&& servletContext == null) {

  5.      ServletWebServerFactory factory = getWebServerFactory();

  6.      this.webServer = factory.getWebServer(getSelfInitializer());

  7.   }

  8.   elseif(servletContext != null) {

  9.      try{

  10.         getSelfInitializer().onStartup(servletContext);

  11.      }

  12.      catch(ServletException ex) {

  13.         thrownewApplicationContextException("Cannot initialize servlet context",

  14.               ex);

  15.      }

  16.   }

  17.   initPropertySources();

  18. }

factory.getWebServer(getSelfInitializer());他是通过工厂的方式创建的。

  1. publicinterfaceServletWebServerFactory{

  2.   WebServer getWebServer(ServletContextInitializer... initializers);

  3. }

可以看到 它是一个接口,为什么会是接口。因为我们不止是Tomcat一种web容器。

我们看到还有Jetty,那我们来看TomcatServletWebServerFactory:

  1. @Override

  2. publicWebServer getWebServer(ServletContextInitializer... initializers) {

  3.   Tomcat tomcat = newTomcat();

  4.   File baseDir = (this.baseDirectory != null) ? this.baseDirectory

  5.         : createTempDir("tomcat");

  6.   tomcat.setBaseDir(baseDir.getAbsolutePath());

  7.   Connector connector = newConnector(this.protocol);

  8.   tomcat.getService().addConnector(connector);

  9.   customizeConnector(connector);

  10.   tomcat.setConnector(connector);

  11.   tomcat.getHost().setAutoDeploy(false);

  12.   configureEngine(tomcat.getEngine());

  13.   for(Connector additionalConnector : this.additionalTomcatConnectors) {

  14.      tomcat.getService().addConnector(additionalConnector);

  15.   }

  16.   prepareContext(tomcat.getHost(), initializers);

  17.   return getTomcatWebServer(tomcat);

  18. }

那这块代码,就是我们要寻找的内置Tomcat,在这个过程当中,我们可以看到创建Tomcat的一个流程。

如果不明白的话, 我们在用另一种方式来理解下,大家要应该都知道stater举点例子。

  1. <dependency>

  2.    <groupId>org.springframework.boot</groupId>

  3.    <artifactId>spring-boot-starter-data-redis</artifactId>

  4. </dependency>

  5. <dependency>

  6.    <groupId>org.springframework.boot</groupId>

  7.    <artifactId>spring-boot-starter-freemarker</artifactId>

  8. </dependency>

首先自定义一个stater。

  1. <parent>

  2.    <groupId>org.springframework.boot</groupId>

  3.    <artifactId>spring-boot-starter-parent</artifactId>

  4.    <version>2.1.4.RELEASE</version>

  5.    <relativePath/>

  6. </parent>

  7. <groupId>com.zgw</groupId>

  8. <artifactId>gw-spring-boot-starter</artifactId>

  9. <version>1.0-SNAPSHOT</version>

  10. <dependencies>

  11.    <dependency>

  12.        <groupId>org.springframework.boot</groupId>

  13.        <artifactId>spring-boot-autoconfigure</artifactId>

  14.    </dependency>

  15. </dependencies>

我们先来看maven配置写入版本号,如果自定义一个stater的话必须依赖spring-boot-autoconfigure这个包,我们先看下项目目录。

  1. publicclassGwServiceImpl  implementsGwService{

  2.    @Autowired

  3.    GwProperties properties;

  4.    @Override

  5.    publicvoidHello()

  6.    {

  7.        String name=properties.getName();

  8.        System.out.println(name+"说:你们好啊");

  9.    }

  10. }

我们做的就是通过配置文件来定制name这个是具体实现。

  1. @Component

  2. @ConfigurationProperties(prefix = "spring.gwname")

  3. publicclassGwProperties{

  4.    String name="zgw";

  5.    publicString getName() {

  6.        return name;

  7.    }

  8.    publicvoid setName(String name) {

  9.        this.name = name;

  10.    }

  11. }

这个类可以通过@ConfigurationProperties读取配置文件。

  1. @Configuration

  2. @ConditionalOnClass(GwService.class)  //扫描类

  3. @EnableConfigurationProperties(GwProperties.class) //让配置类生效

  4. publicclassGwAutoConfiguration{

  5.    /**

  6.    * 功能描述 托管给spring

  7.    * @author zgw

  8.    * @return

  9.    */

  10.    @Bean

  11.    @ConditionalOnMissingBean

  12.    publicGwService gwService()

  13.    {

  14.        returnnewGwServiceImpl();

  15.    }

  16. }

这个为配置类,为什么这么写因为,spring-boot的stater都是这么写的,我们可以参照他仿写stater,以达到自动配置的目的,然后我们在通过spring.factories也来进行配置。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration

然后这样一个简单的stater就完成了,然后可以进行maven的打包,在其他项目引入就可以使用。

作者:stackOverFlow stackOverFlow

cnblogs.com/jing99/p/11504113.html

十大城市,程序员工作内推群

十大城市,程序员工作内推群

近期技术热文

重磅:某国产操作系统发布,称完全可替代Windows 7

大数据量 PageHelper 分页 性能瓶颈, 解决方案!

是时候装逼了,试试 IDEA 解决 Maven 依赖冲突的高能神器!


第2版:互联网大厂面试题

包括 Java 集合、JVM、多线程、并发编程、设计模式、算法调优、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、Python、HTML、CSS、Vue、React、JavaScript、Android 大数据、阿里巴巴等大厂面试题等、等技术栈!

阅读原文: 高清 3625页大厂面试题  PDF

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存